package org.kfive.nova.lock;

/*
 * Copyright (c) 2016 lizhanwu.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import redis.clients.jedis.Jedis;

/*
 * <p>
 * RedisDistributedLock.java
 *
 * Application:
 * NO
 *
 * Limited:
 * NO
 *
 * Warning:
 * NO
 *-----------------------------------------------------------------------------
 *         VERSION       DATE            BY       CHANGE/COMMENT
 * -----------------------------------------------------------------------------
 *          1.0      2016-12-1310:08     lizhanwu      create
 * -----------------------------------------------------------------------------
 * </p>
 */
public class RedisDistributedLock {

    private static final int TIMEOUTMSECS = 10000;

    private static final String LOCK = "LOCK";
    /**
     * 获取锁
     * SETNX语义：若存在KEY则返回0，若不存在则写入KEY,VALUE
     * 使用Redis命令SETNX,Redis保证只会有一个请求操作成功，从而实现分布式锁
     * 若SETNX返回1则表示获取锁成功，若返回0则获取锁失败；失败时需要判断锁是否已超时，若已经超时说明已经获得锁的客户端已经宕机或挂起，可以重新申请获得锁，
     * 通过GETSET方法重设LOCK的超时时间，如果返回的oldValue和上次GET获得的value相同则表明获取所成功，否则失败
     * @return
     */
    public synchronized boolean acquireLock(Jedis jedis){
        int timeout = TIMEOUTMSECS;
        while (timeout>=0){
            long expires = System.currentTimeMillis() + TIMEOUTMSECS +1;
            String expiresStr = String.valueOf(expires);
            //设置LOCK，获取锁
            if(jedis.setnx(LOCK,expiresStr)==1){
                return true;
            }
            //获取锁失败，判断锁是否超时
            String currentValueStr = jedis.get(LOCK);
            if(currentValueStr!=null && Long.parseLong(currentValueStr)< System.currentTimeMillis()){
                //锁超时，若没有其他用户获得锁则GETSET的value值同GET的value值相同
                String oldValueStr = jedis.getSet(LOCK,expiresStr);
                if(oldValueStr!=null && oldValueStr.equals(currentValueStr)){
                    return true;
                }
            }
            timeout -= 100;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    public synchronized void releaseLock(Jedis jedis){
        String currentValue = jedis.get(LOCK);
        if(currentValue!=null && Long.parseLong(currentValue)>System.currentTimeMillis()){
            //说明锁未超时，需要删除LOCK
            jedis.del(LOCK);
        }
        //否则说明锁已经超时不用删除
    }

}
